using CfgTable;
using Cysharp.Threading.Tasks;
using IQIGame.Onigao.Framework;
using IQIGame.Onigao.Game;
using NetProtocol.POD;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace IQIGame.Onigao.GamePlay
{
    public struct EntityInteractResult
    {
        public static readonly EntityInteractResult Empty =
            new EntityInteractResult(EntityInteractOptionResultType.None, 0);

        public EntityInteractOptionResultType type;
        public int value;

        public EntityInteractResult(EntityInteractOptionResultType type, int value)
        {
            this.type = type;
            this.value = value;
        }
    }

    public static class GameScenePlayUtil
    {
        public const float DefaultRaycastEntityHeight = 1.5f;

        /// <summary>
        /// 查询地图迷雾实体的方法
        /// 经常访问的方法，action先创建出来避免GC
        /// </summary>
        private static System.Func<TViewEntity, bool> _fogSearchFunc => (entity) =>
        {
            return entity.mapSymbolCid != 0 && TableCenter.mapUiEntity.Get(entity.mapSymbolCid).Type == MapUiEntityType.Fog;
        };

        /// <summary>
        /// 根据地面MeshCollider校准实体高度
        /// </summary>
        /// <param name="x">单位：米</param>
        /// <param name="y">单位：米</param>
        /// <param name="z">单位：米</param>
        /// <param name="hitInfo"></param>
        /// <returns></returns>
        public static bool TryGroundRaycast(float x, float y, float z, out RaycastHit hitInfo)
        {
            return TryGroundRaycast(x, y, z, DefaultRaycastEntityHeight, out hitInfo);
        }

        public static bool TryGroundRaycast(Vector3 position, out RaycastHit hitInfo)
        {
            return TryGroundRaycast(position.x, position.y, position.z, DefaultRaycastEntityHeight, out hitInfo);
        }

        public static bool TryGroundRaycast(Vector3 position, float entityHeight, out RaycastHit hitInfo)
        {
            return TryGroundRaycast(position.x, position.y, position.z, entityHeight, out hitInfo);
        }

        public static bool TryGroundRaycast(float x, float y, float z, float entityHeight, out RaycastHit hitInfo)
        {
            float entityHalfHeight = entityHeight * 0.5f;
            float rayMaxDistance = entityHalfHeight + 1.5f;
            var raycastPosition = new Vector3(x, y + entityHalfHeight, z);
            if (Physics.Raycast(raycastPosition, Vector3.down, out hitInfo, rayMaxDistance,
                    LayerMaskConsts.Layer_Ground))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 当实体可交互时，获取交互结果
        /// 如果实体身上没有任务，商店等固定功能，则直接触发默认交互逻辑
        /// 否则打开选项面板，由玩家选择触发什么逻辑
        /// 
        /// 注意该方法会返回触发结果，但不会执行任何相关逻辑，调用者根据结果自行决定处理后续逻辑
        /// 注意如果实体交互类型为0，或根据交互类型无法取到交互配置时，无需调用此接口，因为此时表明实体不可交互
        /// </summary>
        /// <param name="quests">实体身上的所有任务</param>
        /// <param name="interactCfg">交互类型配置</param>
        /// <param name="interactEntity">正在交互的实体</param>
        /// <returns>返回触发结果</returns>
        public static async UniTask<EntityInteractResult> InteractWithEntity(IReadOnlyList<int> quests, IReadOnlyList<DialogReference> dialogs,
            CfgEntityInteractType interactCfg, TViewEntity interactEntity, bool showDefaultOption)
        {
            if (interactCfg == null)
            {
                LogGame.LogError("和实体交互失败：实体交互类型配置为空。请注意：实体交互类型为0时，不要调用该接口。");
                return default;
            }

            if (interactEntity is not IConditionPropFetcher)
            {
                LogGame.LogError("和实体交互失败：实体不支持条件检查（实现IConditionPropFetcher接口）。");
                return default;
            }

            List<int> validQuests = ListPool<int>.Get();
            if (quests != null)
            {
                foreach (int cid in quests)
                {
                    if (QuestModule.Instance.IsAcceptable(cid, interactEntity))
                    {
                        validQuests.Add(cid);
                    }
                }
            }
            List<int> validDialogs = ListPool<int>.Get();
            if (dialogs != null)
            {
                foreach (var d in dialogs)
                {
                    (bool isOK, int failId) = GameplayAreaManager.Instance.curSceneArea.CheckCondition(d.activeCondition, interactEntity);
                    if (isOK)
                    {
                        validDialogs.Add(d.dialog);
                    }
                }
            }

            EntityInteractResult result = EntityInteractResult.Empty;
            //打开交互选项面板
            if (validQuests.Count > 0 || validDialogs.Count > 0)
            {
                await ManagerCenter.UI.ShowWindowAsync<UIInteractOption>();
                var interactUI = ManagerCenter.UI.GetEntityUI<UIInteractOption>();
                var bindCom = interactEntity.GetComponent<EntityBindPointComponent>(ETComponentType.EntityBindPoint);
                var pointTrans = bindCom.GetBindPoint(EntityBindPointCommonType.Top);
                var uiResult = await interactUI.ShowOptions(validQuests, validDialogs, interactCfg,
                    pointTrans == null ? Vector3.zero : pointTrans.position);
                if (!uiResult.isCanceled)
                {
                    LogGame.Log($"点击了交互面板的{(uiResult.index == -1 ? "直接交互按钮" : $"任务按钮，index：{uiResult.index}")}");
                    if (uiResult.type == UIInteractOption.ResultType.Default) //点击了默认交互
                    {
                        result.type = EntityInteractOptionResultType.Default;
                    }
                    else if (uiResult.type == UIInteractOption.ResultType.Quest) //点击了任务
                    {
                        result.type = EntityInteractOptionResultType.Quest;
                        int selectedQuestCid = validQuests[uiResult.index];
                        result.value = selectedQuestCid;
                    }
                    else if (uiResult.type == UIInteractOption.ResultType.StoryDialog) //点击了剧情对话
                    {
                        result.type = EntityInteractOptionResultType.StoryDialog;
                        int selectedDialogCid = validDialogs[uiResult.index];
                        result.value = selectedDialogCid;
                    }
                }
                else
                {
                    result.type = EntityInteractOptionResultType.Cancel;
                }
            }
            else
            {
                result.type = EntityInteractOptionResultType.Default;
            }

            //回收list
            ListPool<int>.Put(validQuests);
            ListPool<int>.Put(validDialogs);
            return result;
        }

        /// <summary>
        /// 计算交互优先级(值越小优先级越高)
        /// </summary>
        /// <param name="heroInteractExtendRange">英雄额外交互距离，例如远程攻击范围</param>
        /// <param name="heroUnityPos">英雄位置</param>
        /// <param name="heroForward">英雄朝向</param>
        /// <param name="entityInteractTypeCid">实体交互表配置id</param>
        /// <param name="entityUnityPos">实体位置</param>
        /// <param name="entityObstacle">实体阻挡信息，没有传null</param>
        /// <returns></returns>
        public static float GetEntityInteractPriority(float heroInteractExtendRange, Vector3 heroUnityPos,
            Vector3 heroForward,
            int entityInteractTypeCid, Vector3 entityUnityPos, ObstacleInfo entityObstacle)
        {
            if (entityInteractTypeCid == 0)
            {
                return float.MaxValue;
            }

            var entityBodyRadius =
                entityObstacle != null ? GameMathUtil.LogicValue2Float(entityObstacle.maxDisToCenter) : 0;

            //玩家化身和实体的距离
            float heroEntityDistance = Vector3.Distance(heroUnityPos, entityUnityPos);
            //距离优先级
            float distancePriority = heroEntityDistance;

            // 交互距离 = 默认交互距离和实际距离中的最大值
            var interactDistance = System.Math.Max(ViewEntityConst.InteractDistance,
                entityBodyRadius + ViewEntityConst.CharacterRadius + 0.1f);
            // 距离优先级等于距离，越小越优先，计算时要减去实体本身的半径。防止大型实体权重异常问题
            distancePriority -= entityBodyRadius;

            // 如果是战斗等有额外范围加成的交互，那么需要加上攻击者（主角）的攻击范围
            var interactCfg = TableCenter.entityInteractType.Get(entityInteractTypeCid);
            if (interactCfg.IsExtendRange)
            {
                interactDistance += GameMathUtil.LogicValue2Float(heroInteractExtendRange);
            }

            //距离太远无法交互
            if (heroEntityDistance > interactDistance)
            {
                return float.MaxValue;
            }

            //计算角度
            var targetDir = entityUnityPos - heroUnityPos;
            targetDir.y = 0;
            var angle = Vector3.Angle(heroForward, targetDir);
            // 根据角度和距离计算优先级, 然后取最小值
            // 交互优先级 = 角度 * 角度优先级 + 距离优先级
            var priority = float.MaxValue;
            if (interactCfg != null)
            {
                foreach (var kv in interactCfg.Weight) //这里Weight是取名问题，实际是优先级，权重一般是越大越优先的
                {
                    //从小到大计算角度权重的值，以符合当前角度的最大值为准。
                    //例如配置了1°，10°，100°，200°，而当前角度是150°，则实际权重是100°的权重
                    if (angle <= kv.Key)
                    {
                        priority = angle * kv.Value + distancePriority;
                    }
                }
            }

            return priority;
        }

        /// <summary>
        /// 刷新所有实体跟踪，包括任务追踪
        /// </summary>
        /// <returns></returns>
        public static async UniTask ShowEntityTrack()
        {
            HideQuestTrack();
            var questCid = GameDataCenter.PlayerData.quest.trackQuestCid;
            if (questCid == 0)
            {
                return;
            }
            List<TViewEntity> entities = ListPool<TViewEntity>.Get();

            var data = UD_QuestTrack.Create();
            var questConfig = TableCenter.quest.Get(questCid);
            int targetRegionCid = 0;
            if (questConfig.TargetRangeSize.Count == 0) //策划需求:只有在追踪任务目标点时才显示Mark
            {
                GetQuestTrackEntities(entities, out targetRegionCid);
            }

            data.questCid = questCid;
            data.otherRegionCid = entities.Count > 0 ? 0 : targetRegionCid;

            await ManagerCenter.UI.ShowWindowAsync<UIQuestTrack>(data);
            var trackingEntity = GameplayAreaManager.Instance.curSceneArea.GetTrackingEntity();
            if (trackingEntity != null)
            {
                if (!entities.Contains(trackingEntity))
                {
                    entities.Add(trackingEntity);
                }
            }

            if (entities.Count > 0)
            {
                UIQuestTrack uiTrack = ManagerCenter.UI.GetEntityUI<UIQuestTrack>();
                for (var i = 0; i < entities.Count; i++)
                {
                    var entity = entities[i];
                    uiTrack.AddMark(entity);
                }
            }

            ListPool<TViewEntity>.Put(entities);
        }

        /// <summary>
        /// 获取当前任务追踪的实体
        /// </summary>
        /// <param name="results">追踪结果，传入空List，调用方法时填充数据，建议使用ListPool获取，并在使用完后Put回去</param>
        /// <param name="regionCid">任务追踪实体所属地区cid</param>
        public static void GetQuestTrackEntities(List<TViewEntity> results, out int regionCid)
        {
            var cfgQuest = GameDataCenter.PlayerData.quest.trackQuestCfg;
            if (cfgQuest == null)
            {
                regionCid = 0;
                return;
            }

            regionCid = cfgQuest.TargetRegion;
            bool isTargetInCurRegion = false;
            var curArea = GameplayAreaManager.Instance.curSceneArea;
            if (curArea != null)
            {
                isTargetInCurRegion = cfgQuest.TargetRegion == curArea.curRegionCid;
            }

            if (isTargetInCurRegion)
            {
                foreach (var entityId in cfgQuest.TargetShowEntity)
                {
                    var entity = curArea.GetEntityByMcid(entityId);
                    if (entity != null)
                    {
                        results.Add(entity);
                    }
                }
            }
        }

        public static void HideQuestTrack()
        {
            ManagerCenter.UI.CloseWindow<UIQuestTrack>();
        }

        /// <summary>
        /// 在指定点附近的一个圆里，随机找N个点，点尽量贴到地面上。
        /// </summary>
        /// <param name="center"></param>
        /// <param name="radius"></param>
        /// <param name="count"></param>
        /// <param name="points"></param>
        public static void GetRandomPointInCircleOnGround(Vector3 center, float radius, int count, List<Vector3> points)
        {
            for (int i = 0; i < count; i++)
            {
                float theta = Random.Range(0f, 2f * Mathf.PI);
                float r = Mathf.Sqrt(Random.Range(0f, 1f)) * radius;

                float x = center.x + r * Mathf.Cos(theta);
                float z = center.z + r * Mathf.Sin(theta);

                var pointInPlane = new Vector3(x, center.y, z);
                if (Physics.Raycast(center, Vector3.down, out RaycastHit hitInfo,
                        GameGlobalConst.SceneDropDetectGroundDistance, LayerMaskConsts.Layer_Ground))
                {
                    pointInPlane.y = hitInfo.point.y;
                }

                points.Add(pointInPlane);
            }
        }

        public static string GetDropEffectPathByItem(int itemCid, bool isLightBeam)
        {
            int resCid = 0;
            if (itemCid == ItemConst.ItemCid_Gold)
            {
                resCid = isLightBeam ? DropEffectResId.GoldBeam : DropEffectResId.Gold;
            }
            else if (itemCid == ItemConst.ItemCid_Diamond)
            {
                resCid = isLightBeam ? DropEffectResId.DiamondBeam : DropEffectResId.Diamond;
            }
            else
            {
                var cfgItem = TableCenter.item.Get(itemCid);
                switch (cfgItem.Quality)
                {
                    case 1:
                        resCid = isLightBeam ? DropEffectResId.Quality1Beam : DropEffectResId.Quality1;
                        break;
                    case 2:
                        resCid = isLightBeam ? DropEffectResId.Quality2Beam : DropEffectResId.Quality2;
                        break;
                    case 3:
                        resCid = isLightBeam ? DropEffectResId.Quality3Beam : DropEffectResId.Quality3;
                        break;
                    case 4:
                        resCid = isLightBeam ? DropEffectResId.Quality4Beam : DropEffectResId.Quality4;
                        break;
                    case 5:
                        resCid = isLightBeam ? DropEffectResId.Quality5Beam : DropEffectResId.Quality5;
                        break;
                    default:
                        break;
                }
            }

            if (resCid == 0)
            {
                return null;
            }

            var cfgResource = TableCenter.resource.Get(resCid);
            return cfgResource.Url;
        }

        /// <summary>
        /// 获取小地图显示的实体
        /// </summary>
        /// <param name="entities"></param>
        public static void GetSmallMapEntities(List<TViewEntity> entities)
        {
            if (GameplayAreaManager.Instance.curArea is IGameplaySceneArea sceneArea)
            {
                sceneArea.GetEntitiesAround(entities, GameGlobalConst.SmallMapEntitySearchRadius);
            }
        }

        public static bool IsPlayerMoving()
        {
            if (GameplayAreaManager.Instance.curArea is IGameplaySceneArea sceneArea)
            {
                return sceneArea.IsPlayerMoving();
            }

            return false;
        }

        /// <summary>
        /// 获取当前地区所有地图迷雾实体
        /// </summary>
        /// <param name="fogList"></param>
        public static void GetAllMapFog(List<TViewEntity> fogList)
        {
            var curArea = GameplayAreaManager.Instance.curSceneArea;
            curArea.SearchEntities(fogList, _fogSearchFunc);
        }

        /// <summary>
        /// 游戏内各GameplayArea，各地区之间传送
        /// 统一对外的传送接口
        /// </summary>
        /// <param name="targetRegionCid"></param>
        /// <param name="targetPortalMcid"></param>
        /// <returns></returns>
        public static async UniTask<bool> TeleportInGame(int targetRegionCid, int targetPortalMcid)
        {
            int targetLevelCid = GetLevelCidByRegionCid(targetRegionCid);
            int curLevelCid = 0;
            if (GameplayAreaManager.Instance.curAreaType == GameplayAreaType.Level)
            {
                curLevelCid = LevelPlayModule.Instance.levelPlay.cfgLevel.Id;
            }
            //内部传送（主城内部，同一个关卡内部）
            //主城内部传送targetLevelCid和curLevelCid都是0
            if (targetLevelCid == curLevelCid)
            {
                await GameplayAreaManager.Instance.curSceneArea.TeleportToRegion(targetRegionCid, targetPortalMcid);
                return true;
            }
            //跨GameplayArea传送
            //从主城到关卡
            if (curLevelCid == 0)
            {
                return await EnterLevel(targetLevelCid, targetRegionCid, targetPortalMcid);
            }
            //从关卡到主城，或从关卡A到关卡B
            //都会发送离开关卡的请求，然后在CLevelLogicModule.SC_NotifyLeave里判断是切换到主城，还是另一个关卡
            return await LeaveLevel(targetRegionCid, targetPortalMcid, targetLevelCid);
        }

        /// <summary>
        /// 离开关卡，回到主城等非关卡模块
        /// </summary>
        /// <param name="regionAfterLeave">离开关卡后的目标地区</param>
        /// <param name="portalMcidAfterLeave">离开关卡后的目标地区里的标记点mcid</param>
        /// <returns>是否成功</returns>
        public static async UniTask<bool> LeaveLevel(int regionAfterLeave, int portalMcidAfterLeave, int anotherLevelCid)
        {
            //先创建一个任务，场景切换完毕后会在GameplayAreaModule会自动完成它
            //由于CS_Leave请求执行的过程中就会开始切换场景，所以任务必须提前创建，否则等消息结束后可能切换场景已经完成了，再创建switchLevelTask也就没用了
            var switchLevelTask = GameplayAreaManager.Instance.NewLevelSwitchTask();
            //发送请求和切换场景不一定哪个先完成，所以这里不要等待网络协议，同步调用协议后，直接await switchLevelTask，否则切换场景如果在协议之前完成，switchLevelTask有可能已经入池回收了，await会出问题
            //而网络协议完成后，如果失败，switchLevelTask一定会被cancel，如果成功，也一定会执行到切换场景后的complete switchLevelTask，也就是说，只await switchLevelTask就足够了
            HandleLeaveLevelRequest(regionAfterLeave, portalMcidAfterLeave, anotherLevelCid).Forget();
            try
            {
                //等待切换关卡的任务
                await switchLevelTask;
            }
            catch (System.OperationCanceledException) //网络协议失败后会走取消，如果成功必然执行场景切换并完成switchLevelTask
            {
                return false;
            }
            catch (System.Exception e) //发生取消任务以外的其他异常，视作切换失败
            {
                LogGame.LogException(e);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 请求离开关卡，如果请求失败cancel切换关卡任务
        /// </summary>
        /// <param name="regionAfterLeave"></param>
        /// <param name="portalMcidAfterLeave"></param>
        /// <returns></returns>
        private static async UniTask HandleLeaveLevelRequest(int regionAfterLeave, int portalMcidAfterLeave, int anotherLevelCid)
        {
            var result = await CLevelLogicModule.NetCore.CS_Leave(regionAfterLeave, portalMcidAfterLeave, anotherLevelCid);
            if (result.IsFail)
            {
                Debug.LogError("离开关卡失败");
                //如果请求失败，取消之前创建的请求
                GameplayAreaManager.Instance.CancelLevelSwitchTask();
            }
        }

        /// <summary>
        /// 进入关卡
        /// </summary>
        /// <param name="levelCid">目标关卡cid</param>
        /// <param name="regionCid">目标关卡内的地区icd</param>
        /// <param name="portalMcid">目标地区内的标记点mcid</param>
        /// <returns>是否成功</returns>
        public static async UniTask<bool> EnterLevel(int levelCid, int regionCid, int portalMcid)
        {
            //代码逻辑的细节参考LeaveLevel的注释，本方法与其基本逻辑一致
            var switchLevelTask = GameplayAreaManager.Instance.NewLevelSwitchTask();
            HandleEnterLevelRequest(levelCid, regionCid, portalMcid).Forget();
            try
            {
                await switchLevelTask;
            }
            catch (System.OperationCanceledException)
            {
                return false;
            }
            catch (System.Exception e)
            {
                LogGame.LogException(e);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 从一个关卡到另一个关卡
        /// 注意写到这里只是为了和EnterLevel和LeaveLevel对应，好查代码，但这个方法是专用方法
        /// 外部不要随意调用
        /// </summary>
        /// <param name="levelCid"></param>
        /// <param name="regionCid"></param>
        /// <param name="portalMcid"></param>
        public static void EnterLevelAfterLeftPreviousLevel(int levelCid, int regionCid, int portalMcid)
        {
            HandleEnterLevelRequest(levelCid, regionCid, portalMcid).Forget();
        }

        /// <summary>
        /// 请求进入关卡，如果请求失败cancel切换关卡任务
        /// </summary>
        /// <param name="levelCid"></param>
        /// <param name="regionCid"></param>
        /// <param name="portalMcid"></param>
        /// <returns></returns>
        private static async UniTask HandleEnterLevelRequest(int levelCid, int regionCid, int portalMcid)
        {
            RequestEnterLevelPOD enterLevelPod = new RequestEnterLevelPOD()
            {
                levelCid = levelCid,
                clientTime = GameServerModule.Instance.GetTimeStamp(GameDataCenter.ClientDisplayTime),
                local = Application.isEditor,
                regionCid = regionCid,
                portalId = portalMcid,
            };
            var result = await LevelModule.NetCore.CS_requestEnter(enterLevelPod);
            if (result.IsFail)
            {
                Debug.LogError("进入关卡失败");
                GameplayAreaManager.Instance.CancelLevelSwitchTask();
            }
        }

        public static int GetLevelCidByRegionCid(int regionCid)
        {
            foreach (var cfgLevel in TableCenter.level.DataList)
            {
                if (cfgLevel.Regions.Contains(regionCid))
                {
                    return cfgLevel.Id;
                }
            }
            return 0;
        }
    }
}